/*******************************************************************************
* Copyright (c) 2008, Jeferson Jose de Miranda                                 *
* UDESC - Universidade do Estado de Santa Catarina                             *
* All rights reserved.                                                         *
*                                                                              *
* Redistribution and use in source and binary forms, with or without           *
* modification, are permitted provided that the following conditions are met:  *
*     * Redistributions of source code must retain the above copyright         *
*       notice, this list of conditions and the following disclaimer.          *
*     * Redistributions in binary form must reproduce the above copyright      *
*       notice, this list of conditions and the following disclaimer in the    *
*       documentation and/or other materials provided with the distribution.   *
*     * Neither the name of the Universidade do Estado de Santa Catarina nor   *
*       the names of its contributors may be used to endorse or promote        *
*       products derived from this software without specific prior written     *
*       permission.                                                            *
*                                                                              *
* THIS SOFTWARE IS PROVIDED BY JEFERSON JOSE DE MIRANDA "AS IS" AND ANY        *
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED    *
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE       *
* DISCLAIMED. IN NO EVENT SHALL JEFERSON JOSE DE MIRANDA BE LIABLE FOR ANY     *
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES   *
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; *
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND  *
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   *
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF     *
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.            *
*******************************************************************************/

/**
 * \file ROIGroup.hpp
 *
 * \brief Manages a set of ROIs.
 *
 * \author Jeferson Jose de Miranda
 */

#ifndef ROIGROUP_HPP
#define ROIGROUP_HPP

// OpenCV main header.
#include <cv.h>
// Move-In RGG image.
#include "RGBImage.hpp"
// Operations between OpenCV's 2D points (CvRect).
#include "Point2DMath.hpp"
// Exceptions.hpp groups Move-In exception messages.
#include "Exceptions.hpp"

#define INSIDE      50
#define OUTSIDE     51
#define OVERLAPPING 52
#define NOT_FOUND   53

#define EDGE_LOCK   55
#define CENTER_LOCK 56
#define UNLOCKED    57

namespace MoveIn
{
    /**
    * \class ROIGroup
    *
    * \brief Manages a set of ROIs.
    */
    class ROIGroup
    {
        private:
        CvSeq *roi_group;
        CvMemStorage *storage;
        int x_begin;
        int x_end;
        int y_begin;
        int y_end;
        uchar lock;

        void setCenterCoordinates();

        public:
        ROIGroup(uchar lock = UNLOCKED);

        ~ROIGroup();

        void append(CvRect roi);

        void insert(CvRect roi,
                    unsigned int index);

        void remove(unsigned int index);

        CvRect getROI(int index);

        CvRect getFirstROI();

        CvRect getLastROI();

        unsigned int size();

        void reset();

        bool equals(CvRect roi,
                    unsigned int index);

        int realIndex(int index);

        int indexOf(CvRect roi);

        void clone(ROIGroup *rg);

        void print(RGBImage *source);

        void printBorders(RGBImage *source);

        void printCenter(RGBImage *source);

        uchar position(RGBImage *source, unsigned index);

        static uchar position(Image *source, CvRect roi);

        void translate(int x, int y);

        void translateX(int x);

        void translateY(int y);

        void setCenter(int x, int y);

        CvPoint getCenter();

        unsigned int getWidth();

        unsigned int getHeight();
    };

/*******************************************************************************
* MoveIn::ROIGroup::ROIGroup                                                   *
*******************************************************************************/

    ROIGroup::ROIGroup(uchar lock)
    {
        storage = NULL;
        storage = cvCreateMemStorage(0);
        if(storage == NULL)
        {
            throw new OutOfMemory();
        }
        else
        {
            roi_group = cvCreateSeq(0,
                                    sizeof(*roi_group),
                                    sizeof(CvRect),
                                    storage);
            x_begin = -1;
            x_end = -1;
            y_begin = -1;
            y_end = -1;

            this->lock = lock;
        }
    }

/*******************************************************************************
* MoveIn::ROIGroup::~ROIGroup                                                  *
*******************************************************************************/

    ROIGroup::~ROIGroup()
    {
        cvClearSeq(roi_group);
        cvReleaseMemStorage(&storage);
    }

/*******************************************************************************
* MoveIn::ROIGroup::append                                                     *
*******************************************************************************/

    void ROIGroup::append(CvRect roi)
    {
        cvSeqPush(roi_group, &roi);
        setCenterCoordinates();
    }

/*******************************************************************************
* MoveIn::ROIGroup::insert                                                     *
*******************************************************************************/

    void ROIGroup::insert(CvRect roi, unsigned int index)
    {
        if(index > (unsigned int) roi_group->total)
        {
            throw new OutOfBounds();
        }
        else
        {
            cvSeqInsert(roi_group, index, &roi);
            setCenterCoordinates();
        }
    }

/*******************************************************************************
* MoveIn::ROIGroup::remove                                                     *
*******************************************************************************/

    void ROIGroup::remove(unsigned int index)
    {
        if(index >= (unsigned int) roi_group->total)
        {
            throw new OutOfBounds();
        }
        else
        {
            cvSeqRemove(roi_group, index);
            setCenterCoordinates();
        }
    }

/*******************************************************************************
* MoveIn::ROIGroup::getROI                                                     *
*******************************************************************************/

    CvRect ROIGroup::getROI(int index)
    {
        try
        {
            int idx = realIndex(index);
            CvRect *roi = (CvRect*) cvGetSeqElem(roi_group, idx);
            return cvRect(roi->x, roi->y, roi->width, roi->height);
        }
        catch(std::exception *e)
        {
            throw e;
        }
    }

/*******************************************************************************
* MoveIn::ROIGroup::getFirstROI                                                *
*******************************************************************************/

    CvRect ROIGroup::getFirstROI()
    {
        return getROI(0);
    }

/*******************************************************************************
* MoveIn::ROIGroup::getLastROI                                                 *
*******************************************************************************/

    CvRect ROIGroup::getLastROI()
    {
        if(roi_group->total == 0)
        {
            throw new OutOfBounds();
        }
        else
        {
            return getROI(roi_group->total - 1);
        }
    }

/*******************************************************************************
* MoveIn::ROIGroup::size                                                       *
*******************************************************************************/

    unsigned int ROIGroup::size()
    {
        return roi_group->total;
    }

/*******************************************************************************
* MoveIn::ROIGroup::reset                                                      *
*******************************************************************************/

    void ROIGroup::reset()
    {
        cvClearSeq(roi_group);
        setCenterCoordinates();
    }

/*******************************************************************************
* MoveIn::ROIGroup::equals                                                     *
*******************************************************************************/

    bool ROIGroup::equals(CvRect roi,
                          unsigned int index)
    {
        if(index >= (unsigned int) roi_group->total)
        {
            throw new OutOfBounds();
        }
        else
        {
            CvRect r;
            r = getROI(index);
            if(roi.x == r.x && roi.y == r.y &&
               roi.width == r.width && roi.height == r.height)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
    }

/*******************************************************************************
* MoveIn::ROIGroup::realIndex                                                  *
*******************************************************************************/

    int ROIGroup::realIndex(int index)
    {
        if(size() == 0)
        {
            return -1;
        }
        else if((index % (int) size()) == 0)
        {
            return 0;
        }
        else if(index < 0)
        {
            return size() + (index % (int) size());
        }
        else if(index >= (int) size())
        {
            return index % size();
        }
        else
        {
            return index;
        }
    }

/*******************************************************************************
* MoveIn::ROIGroup::indexOf                                                    *
*******************************************************************************/

    int ROIGroup::indexOf(CvRect roi)
    {
        for(unsigned int i=0; i<size(); i++)
        {
            if(getROI(i).x == roi.x &&
               getROI(i).y == roi.y &&
               getROI(i).width == roi.width &&
               getROI(i).height == roi.height)
            {
                return i;
            }
        }
        return -1;
    }

/*******************************************************************************
* MoveIn::ROIGroup::clone                                                      *
*******************************************************************************/

    void ROIGroup::clone(ROIGroup *rg)
    {
        reset();
        for(unsigned int i=0; i<rg->size(); i++)
        {
            append(rg->getROI(i));
            setCenterCoordinates();
        }
    }

/*******************************************************************************
* MoveIn::ROIGroup::print                                                      *
*******************************************************************************/

    void ROIGroup::print(RGBImage *source)
    {
        CvRect roi;
        for(unsigned int i=0; i<size(); i++)
        {
            if(position(source, i) == INSIDE)
            {
                roi = getROI(i);
                cvRectangle(source->getIplImage(),
                            cvPoint(roi.x, roi.y),
                            cvPoint(roi.x+roi.width, roi.y+roi.height),
                            CV_RGB(255,255,000),
                            1,
                            8,
                            0);
            }
        }
        if(x_begin != -1 && x_end != -1 &&
           y_begin != -1 && y_end != -1)
        {
            cvCircle(source->getIplImage(),
                     getCenter(),
                     3,
                     CV_RGB(255,255,000),
                     5);
        }
    }

/*******************************************************************************
* MoveIn::ROIGroup::printBorders                                               *
*******************************************************************************/

    void ROIGroup::printBorders(RGBImage *source)
    {
        CvRect roi;
        for(unsigned int i=0; i<size(); i++)
        {
            if(position(source, i) == INSIDE)
            {
                roi = getROI(i);
                cvRectangle(source->getIplImage(),
                            cvPoint(roi.x, roi.y),
                            cvPoint(roi.x+roi.width, roi.y+roi.height),
                            CV_RGB(255,255,000),
                            2,
                            8,
                            0);
            }
        }
    }

/*******************************************************************************
* MoveIn::ROIGroup::printCenter                                                *
*******************************************************************************/

    void ROIGroup::printCenter(RGBImage *source)
    {
        if(x_begin != -1 && x_end != -1 &&
           y_begin != -1 && y_end != -1)
        {
            cvCircle(source->getIplImage(),
                     getCenter(),
                     3,
                     CV_RGB(255,255,000),
                     5);
        }
    }

/*******************************************************************************
* MoveIn::ROIGroup::position                                                   *
*******************************************************************************/

    uchar ROIGroup::position(RGBImage *source, unsigned index)
    {
        CvRect roi;
        try
        {
            roi = getROI(index);
        }
        catch(std::exception *e)
        {
            return NOT_FOUND;
        }
        if(roi.x >= 0 &&
           roi.x + roi.width < source->getWidth() &&
           roi.y >= 0 &&
           roi.y + roi.height < source->getHeight())
        {
            return INSIDE;
        }
        return OUTSIDE;
    }

/*******************************************************************************
* MoveIn::ROIGroup::position                                                   *
*******************************************************************************/

    uchar ROIGroup::position(Image *source, CvRect roi)
    {
        if(roi.x >= 0 &&
           roi.x + roi.width < source->getWidth() &&
           roi.y >= 0 &&
           roi.y + roi.height < source->getHeight())
        {
            return INSIDE;
        }
        return OUTSIDE;
    }

/*******************************************************************************
* MoveIn::ROIGroup::translate                                                  *
*******************************************************************************/

    void ROIGroup::translate(int x,
                             int y)
    {
        CvRect roi;
        for(unsigned int i=0; i<size(); i++)
        {
            roi = getROI(i);
            roi.x += x;
            roi.y += y;
            remove(i);
            insert(roi, i);
        }
        setCenterCoordinates();
    }

/*******************************************************************************
* MoveIn::ROIGroup::translateX                                                 *
*******************************************************************************/

    void ROIGroup::translateX(int x)
    {
        CvRect roi;
        for(unsigned int i=0; i<size(); i++)
        {
            roi = getROI(i);
            roi.x += x;
            remove(i);
            insert(roi, i);
        }
        setCenterCoordinates();
    }

/*******************************************************************************
* MoveIn::ROIGroup::translateY                                                 *
*******************************************************************************/

    void ROIGroup::translateY(int y)
    {
        CvRect roi;
        for(unsigned int i=0; i<size(); i++)
        {
            roi = getROI(i);
            roi.y += y;
            remove(i);
            insert(roi, i);
        }
        setCenterCoordinates();
    }

/*******************************************************************************
* MoveIn::ROIGroup::setCenter                                                  *
*******************************************************************************/

    void ROIGroup::setCenter(int x, int y)
    {
        CvPoint center = getCenter();
        x -= center.x;
        y -= center.y;
        translate(x, y);
    }

/*******************************************************************************
* MoveIn::ROIGroup::getCenter                                                  *
*******************************************************************************/

    CvPoint ROIGroup::getCenter()
    {
        CvPoint center;
        center.x = (x_begin + x_end) / 2;
        center.y = (y_begin + y_end) / 2;
        return center;
    }

/*******************************************************************************
* MoveIn::ROIGroup::getWidth                                                   *
*******************************************************************************/

    unsigned int ROIGroup::getWidth()
    {
        if(x_begin == -1 || x_end == -1)
        {
            return 0;
        }
        return x_end - x_begin;
    }

/*******************************************************************************
* MoveIn::ROIGroup::getHeight                                                  *
*******************************************************************************/

    unsigned int ROIGroup::getHeight()
    {
        if(y_begin == -1 || y_end == -1)
        {
            return 0;
        }
        return y_end - y_begin;
    }

/*******************************************************************************
* MoveIn::setCenterCoordinates                                                 *
*******************************************************************************/

    void ROIGroup::setCenterCoordinates()
    {
        if(size() == 0)
        {
            x_begin = x_end = -1;
            y_begin = y_end = -1;
            return;
        }
        CvRect pt = getROI(0);
        x_begin = pt.x;
        x_end = pt.x + pt.width;
        y_begin = pt.y;
        y_end = pt.y + pt.height;
        for(unsigned int i=1; i<size(); i++)
        {
            CvRect pt = getROI(i);
            if(pt.x < x_begin)
            {
                x_begin = pt.x;
            }
            if(pt.x + pt.width > x_end)
            {
                x_end = pt.x + pt.width;
            }
            if(pt.y < y_begin)
            {
                y_begin = pt.y;
            }
            if(pt.y + pt.height > y_end)
            {
                y_end = pt.y + pt.height;
            }
        }
    }
}

#endif
